/* * Copyright 2013 Cloudera Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr.client.solrj.retry; import java.math.RoundingMode; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import com.google.common.base.Preconditions; import com.google.common.math.LongMath; /** * A retry policy that retries with exponentially increasing sleep time between retries until it has * reached either a) the given max number of retries or b) more than the given amount of * time has elapsed since the start of the operation, whichever occurs first. * * For example, with baseSleepTime=100, maxSleepTime=1000 we sleep as follows in each iteration: * random(100..199), random(200..399), random(400..799), min(random(800..1599), 1000), 1000, 1000, * ... and so on. * * This implementation is thread-safe. */ public final class FlexibleBoundedExponentialBackoffRetry implements RetryPolicy { private final int maxRetries; private final long baseSleepTimeNanos; private final long maxSleepTimeNanos; private final long maxElapsedTimeNanos; private final long retriesLimit; /** * @param baseSleepTimeNanos * initial amount of time to wait between retries * @param maxSleepTimeNanos * max time in nanos to sleep on each retry * @param maxRetries * max number of times to retry * @param maxElapsedTimeNanos * max time in nanos to spend across all retries */ public FlexibleBoundedExponentialBackoffRetry( long baseSleepTimeNanos, long maxSleepTimeNanos, int maxRetries, long maxElapsedTimeNanos) { Preconditions.checkArgument(baseSleepTimeNanos >= 0, "baseSleepTimeNanos must not be negative: %s", baseSleepTimeNanos); Preconditions.checkArgument(maxSleepTimeNanos >= baseSleepTimeNanos, "maxSleepNanos: %s must not be less than baseSleepTimeNanos: %s", maxSleepTimeNanos, baseSleepTimeNanos); Preconditions.checkArgument(maxRetries >= 0, "maxRetries must not be negative: %s", maxRetries); Preconditions.checkArgument(maxElapsedTimeNanos >= 0, "maxElapsedTimeNanos must not be negative: %s", maxElapsedTimeNanos); baseSleepTimeNanos = Math.max(1, baseSleepTimeNanos); this.baseSleepTimeNanos = baseSleepTimeNanos; this.maxSleepTimeNanos = maxSleepTimeNanos; this.maxRetries = maxRetries; this.maxElapsedTimeNanos = maxElapsedTimeNanos; this.retriesLimit = LongMath.log2( Long.MAX_VALUE / baseSleepTimeNanos, RoundingMode.DOWN) - 2; } @Override public boolean allowRetry(int retryCount, long elapsedTimeNanos, RetrySleeper sleeper) { if (retryCount >= 0 && retryCount < maxRetries && elapsedTimeNanos < maxElapsedTimeNanos) { try { sleeper.sleepFor(getSleepTime(retryCount), TimeUnit.NANOSECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } return true; } return false; } private long getSleepTime(int retryCount) { if (retryCount < retriesLimit) { long low = baseSleepTimeNanos * (1L << retryCount); long high = 2 * low; long sleepTimeNanos = ThreadLocalRandom.current().nextLong(low, high); return Math.min(maxSleepTimeNanos, sleepTimeNanos); } else { return maxSleepTimeNanos; } } @Override public String toString() { return String.format( "baseSleepTime[secs]:%1.1f, maxSleepTime[secs]:%1.1f, maxRetries:%d, " + "maxElapsedTime[secs]:%1.1f", toSeconds(baseSleepTimeNanos), toSeconds(maxSleepTimeNanos), maxRetries, toSeconds(maxElapsedTimeNanos)); } private double toSeconds(long nanos) { return nanos / (1000.0 * 1000 * 1000); } }